07 RxJS e seu Subject

Próxima Atividade
  • Sugerir melhoria
Play
Current Time 0:00
/
Duration Time 0:00
Remaining Time -0:00
Stream TypeLIVE
Loaded: 0%
Progress: 0%
0:00
Fullscreen
00:00
Mute
Playback Rate
  • 2x
  • 1.5x
  • 1.2x
  • 1x
  • 0.8x
  • 0.7x
1x
Subtitles
  • subtitles off
Captions
  • captions off
Chapters
  • Chapters
HD
Desculpe-nos pelo transtorno, mas este video não pode ser carregado, tente novamente.

Transcrição

Melhoramos a experiência do usuário, agora vamos fazer o mesmo em relação à performance da aplicação. Como estamos disparando o evento keyup, a cada preenchimento do campo de busca o valor digitado será passado para filter, e deste para o Pipe. Então, a cada caractere digitado é feita a aplicação do filtro, e isso prejudica a performance, ainda mais se a quantidade de imagens é muito grande. Pior ainda se estivéssemos realizando requisição AJAX ao servidor.

Seria melhor se, ao digitarmos "farol", fizéssemos uma pausa de 300ms, e aí sim o Pipe fosse aplicado. A vantagem disto é que evitamos a execução de uma série de operações. Para tal, usaremos um Pattern muito comum no JavaScript, para que o filtro seja atualizado somente se pararmos de digitar durante um determinado período de tempo.

A chave para o sucesso desta operação é o Subject do RxJS. Em photo-list.component.ts, criaremos inicialmente a propriedade debounce na classe PhotoListComponent, de tipo Subject, por sua vez do tipo string, que receberá um novo Subject, os quais precisarão ser importados.

Por meio de next(), é possível emitirmos um valor para Subject, que acessamos caso o tenhamos inscrito. Diferentemente do Observable, com o qual podemos inscrever e obter valores, com o Subject podemos, além disso, emitir um valor e escutá-lo, como em:

export class PhotoListComponent implements OnInit {

    photos: Photo[] = [];
    filter: string = '';
    debounce: Subject<string> = new Subject<string>();

    constructor(private activatedRoute: ActivatedRoute) { }

    ngOnInit(): void {
        this.photos = this.activatedRoute.snapshot.data['photos'];

        this.debounce.next('f')
        this.debounce.subscribe(value => alert(value));
    }
}

Do trecho acima, deletaremos o que se segue:

this.debounce.next('f')
this.debounce.subscribe(value => alert(value));

E em photo-list.component.html, no lugar de colocarmos o valor digitado em filter, solicitaremos ao debounce para que seja feito o next(). A cada keyup será emitido um valor:

<input
    class="rounded"
    type="search"
    placeholder="search..."
    autofocus
    (keyup)="debounce.next($event.target.value)"
>

Voltaremos a photo-list.component.ts, em ngOnInit() nos inscreveremos em debounce, e o valor a ser emitido ali será chamado de filter, o qual receberemos, e então indicaremos que this.filter receberá filter.

ngOnInit(): void {
    this.photos = this.activatedRoute.snapshot.data['photos'];
    this.debounce.subscribe(filter => this.filter = filter);
}

Com isso, em vez de jogarmos o valor digitado diretamente em filter, emitiremos um valor de RxJS, a ser escutado pelo subscribe(), o qual atualizará o filtro. O subscribe() será chamado enquanto o valor estiver sendo emitido. Ele é um tanto diferente do HttpClient pois este emite um único valor, e o completa, algo que não ocorre com Subject, por termos criado-o.

Tanto isto é verdade que, se voltarmos ao navegador e recarregarmos a página, o filtro funcionará bem, mesmo sem vantagens, já que estamos tendo mais trabalho, pois pegamos o valor digitado pelo usuário, passando-o para o debounce para que o Subject emita o valor por meio de next(). Tivemos que fazer o subscribe() para então levarmos o valor adiante.

Assim, em photo-list.component.ts importaremos debounceTime de rxjs/operators, junto ao qual uma série de operadores poderá ser importada. A ideia é que, antes do subscribe(), pediremos para o debounce aplicar tal operação, com a estrutura pipe(), em que incluiremos debounceTime, a receber o período de tempo. E desta operação faremos o subscribe().

ngOnInit(): void {
    this.photos = this.activatedRoute.snapshot.data['photos'];
    this.debounce
        .pipe(debounceTime(300))
        .subscribe(filter => this.filter = filter);
}

A grande sacada é que, com esta alteração chamada de Lettable operators no RxJS, por usarmos o debounceTime, quando emitimos um valor no evento keyup, todas as emissões serão ignoradas, sendo consideradas após 300ms. E é isso que será repassado ao subscribe().

O Observable é engenhoso para lidar com situações deste tipo, com fluxos e eventos, e colocamos threshold, um Pipe no que chamamos de debounce, para limitar a quantidade de operações.

Resolvemos o problema de performance, porém ganhamos outro se não tomarmos cuidado: uma vez que o Observable em .subscribe(filter => this.filter = filter) nunca se completa, ele ficará guardando um espaço na memória, e se saímos deste componente e vamos a outra página, a área da memória continuará ocupada, ocasionando em memory leaking (vazamento de memória).

Então, toda vez que houver algo que fique emitindo valores infinitamente, é necessário implementar uma interface, OnDestroy, que também precisa ser implementada em photo-list.component.ts. Ao fazermos isto, o método ngOnDestroy() é acrescentado. Ele faz parte do ciclo de vida de um componente do Angular, sendo chamado toda vez que um objeto é destruído.

Significa que quando sairmos de PhotoListComponent, e ele for destruído, o método será chamado, e faremos o unsubscribe():

export class PhotoListComponent implements OnInit, OnDestroy {

    /* código omitido
    */

    ngOnInit(): void {
        this.photos = this.activatedRoute.snapshot.data['photos'];
        this.debounce
        .pipe(debounceTime(300))
        .subscribe(filter => this.filter = filter);
    }

    ngOnDestroy(): void {
        this.debounce.unsubscribe();
    }
}

Trata-se de uma boa prática, portanto não deixemos de fazê-lo.

Tirar dúvida Próxima Atividade
ícone Angular parte 1: Fundamentos

Angular parte 1: Fundamentos

100%
Aula06 de 07

Melhorando a experiência do usuário

Atividades 7 de 11

  • 01 Projeto da aula anterior
  • 02 Binding de eventos 11min
  • 03 Pipe e implementação 14min
  • 04 Criando nosso próprio pipe
  • 05 Resolvers 12min
  • 06 Implementando Resolvers
  • 07 RxJS e seu Subject 12min
  • 08 Padrão debounce com RxJS
  • 09 Paginação de dados 07min
  • 10 LoadButton 16min
  • 11 Consolidando seu conhecimento

Aulas

Outros Links

Fórum Trocar Curso
Gravatar de Marcos Katsumi Kay

Marcos Katsumi Kay 34.4k xp

Logo da Alura